package contegor.scire.core;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/**
 * With this class we can identify every element in the system.
 * 
 * @version $Revision: 1.11 $
 * @author  Héctor Cirbián Pousa ($Author: hector $)
 */
@Version("$Id: Id.java,v 1.11 2010-02-01 10:55:39 hector Exp $")
public class Id implements Serializable, Comparable<Id> {
  private static final long serialVersionUID = -3762788726708056529L;

  // TODO: Explain RAW_ROOT use.
  public static Id RAW_ROOT = new Id("raw");
  public static Id EMPTY;
  
  static {
    List<IdFragment> aux = Collections.emptyList();
    EMPTY = new Id(aux);
  }

  protected List<IdFragment> fragments;

  public Id() {
    fragments = new ArrayList<IdFragment>();
  }

  public Id(IdFragment fragment) {
    this();
    fragments.add(fragment);
  }
  
  public Id(Id parentId, IdFragment fragment) {
    fragments = new ArrayList<IdFragment>(parentId.fragments);
    fragments.add(fragment);
  }

  public Id(Id parentId, String ... strs) {
    fragments = new ArrayList<IdFragment>(parentId.size() + strs.length);
    for (IdFragment idf: parentId.fragments)
      fragments.add(idf);
    for (String str: strs)
      fragments.add(new StringIdFragment(str));
  }

  public Id(Id parentId, int ... numbers) {
    fragments = new ArrayList<IdFragment>(parentId.size() + numbers.length);
    for (IdFragment idf: parentId.fragments)
      fragments.add(idf);
    for (int i: numbers)
      fragments.add(new IntegerIdFragment(i));
  }

  public Id(IdFragment ... idfs) {
    fragments = Arrays.asList(idfs);
  }

  public Id(String ... idfs) {
    fragments = new ArrayList<IdFragment>(idfs.length);
    for (String str: idfs)
      fragments.add(new StringIdFragment(str));
  }

  public Id(int ... numbers) {
    fragments = new ArrayList<IdFragment>(numbers.length);
    for (int i: numbers)
      fragments.add(new IntegerIdFragment(i));
  }

  public Id(List<IdFragment> fragments) {
    this.fragments = fragments;
  }

  public int size() {
    return fragments.size();
  }

  /**
   * Returns the leaf {@link IdFragment}, the last one.
   */
  public IdFragment getLastIdFragment() {
    return fragments.get(fragments.size() - 1);
  }
  
  /** Returns the {@link IdFragment} at the specified position inside <code>this</code>. */
  public IdFragment getIdFragment(int i) {
    return fragments.get(i);
  }
  
  /** Returns an immutable {@link List} with the {@link IdFragment} that compound this {@link Id}. */
  public List<IdFragment> getIdFragmentsList() {
    return Collections.unmodifiableList(fragments);
  }

  /** Returns the parent Id of <code>this</code>. */
  public Id getParentId() {
    if (this.fragments.size() <= 1)
      return EMPTY;
    return new Id(new ArrayList<IdFragment>(this.fragments.subList(0, this.fragments.size()-1)));
  }
  
  /** Returns true if <code>this</code> is ancestor of the one passed by parameter (rhs). False if not. */
  public boolean isAncestorOf(Id rhs) {
    int size = this.fragments.size();
    if (rhs.fragments.size() <= size)
      return false;
    // It is easy both this and rhs share the same root, so a reverse search is more efficient. 
    for (int i=size-1; i>=0; i--)
      if (!this.fragments.get(i).equals(rhs.fragments.get(i)))
        return false;
    return true;
  }

  /** Returns true if <code>this</code> and the {@link Id} passed by parameter have the same {@link IdFragment} as root. */
  public boolean sameRoot(Id rhs) {
    return this.getIdFragment(0).equals(rhs.getIdFragment(0));
  }

  /** Returns the number of common ancestors between <code>this</code> and the one passed by parameter (rhs). */
  public int commonAncestors(Id rhs) {
    int min = Math.min(this.fragments.size(), rhs.fragments.size());
    int i;
    for (i=0; i<min; i++)
      if (!this.fragments.get(i).equals(rhs.fragments.get(i)))
        return i;
    return i;
  }

  /** Returns true if <code>this</code> is parent of the one passed by parameter (rhs). */
  public boolean isParentOf(Id rhs) {
    return (rhs.size()-1 == this.size()) && this.isAncestorOf(rhs);
  }

  /* (non-Javadoc)
   * @see java.lang.Object#equals(java.lang.Object)
   */
  public boolean equals(Object obj) {
    if (obj == null)
      return false;
    if (this == obj)
      return true;
    if (!(obj instanceof Id))
      return false;
    
    Id rhs = (Id)obj;
    if (this.fragments.size() != rhs.fragments.size())
      return false;

    for (int i=this.fragments.size()-1; i>=0; i--)
      if (!this.fragments.get(i).equals(rhs.fragments.get(i)))
        return false;
    
    return true;
  }

  public int compareTo(Id rhs) {
    if (rhs == null)
      return 1;
    if (this == rhs)
      return 0;
    // We start to compare from the leaf, so the natural order will be alphabetic from the leaf to the root.
    int thisSize = this.fragments.size() - 1;
    int rhsSize  = rhs.fragments.size() - 1;
    while ( (thisSize >= 0) && (rhsSize >= 0)) {
      int comparation = this.fragments.get(thisSize).compareTo(rhs.fragments.get(rhsSize));
      if (comparation != 0)
        return comparation;
      thisSize --;
      rhsSize --;
    }
    return thisSize - rhsSize;
  }

  /* (non-Javadoc)
   * @see java.lang.Object#hashCode()
   */
  public int hashCode() {
    int res = 1;
    for (IdFragment f: fragments)
      res *= f.hashCode();
    return res;
  }

  /* (non-Javadoc)
   * @see java.lang.Object#toString()
   */
  public String toString() {
    StringBuffer buff = new StringBuffer();
    for (IdFragment fragment: this.fragments) {
      buff.append('.');
      buff.append(fragment.toString());
    }
    return buff.toString();
  }

}